package com.eastfair.security.aspect;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.eastfair.annotation.annotation.base.BaseControllerInfo;
import com.eastfair.annotation.annotation.base.IgnoreResponseBodyAdvice;
import com.eastfair.annotation.annotation.security.PreAuth;
import com.eastfair.auth.api.AuthServiceFeign;
import com.eastfair.auth.api.UserAccountServiceFeign;
import com.eastfair.core.base.R;
import com.eastfair.core.context.ContextConstants;
import com.eastfair.core.context.ContextUtil;
import com.eastfair.core.context.StaticContextUtil;
import com.eastfair.core.dynamic.DynamicData;
import com.eastfair.core.exception.ForbiddenException;
import com.eastfair.core.exception.code.ExceptionCode;
import com.eastfair.core.utils.StrPool;
import com.eastfair.security.util.TenantUtil;
import com.eastfair.uid.model.SelfUidDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;


/**
 * controller请求切面操作
 * Created by ligang on 2010/03/02
 */
@Aspect
//@Component
@Order(2)
@Slf4j
public class UrlSecurityPreAuthAspect implements ApplicationContextAware {
    private ApplicationContext ac;

    @Autowired
    TenantUtil tenantUtil;

    @Autowired
    UserAccountServiceFeign userAccountServiceFeign;

    @Autowired
    AuthServiceFeign authServiceFeign;

    @Pointcut("execution(public * com.eastfair..*.controller..*(..))")
    public void BindingResult() {
    }

    @After("BindingResult()")
    public void after(JoinPoint joinPoint) {
        //记录MDC的行为日志
    }

    @AfterReturning(value = "BindingResult()", returning = "rvt")
    public void AfterExec(JoinPoint joinPoint, Object rvt) {
        //controller报错不走这个方法

    }

    @Around("BindingResult()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return joinPoint.proceed();
        }
        //组装BaseControllerInfo vo按需返回
        assembleBaseControllerInfo(joinPoint);
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURI(); // 这个方法只能获得不包含参数的请求url，且只包含相对路径
        String method = request.getMethod();
        //设置全局变量
        globalVariable(request);

        //链路追踪id
//        String traceId = request.getHeader(ContextConstants.TRACE_ID_HEADER);
//        MDC.put(ContextConstants.LOG_TRACE_ID,traceId);
        //ip地址
        MDC.put(ContextConstants.IP_HEADER, StaticContextUtil.getIpAddr());
        handleAuth(joinPoint);

        return joinPoint.proceed();
    }

    /**
     * 设置全局变量
     *
     * @param request
     */
    public void globalVariable(HttpServletRequest request) {
        //将request信息转全局变量
        String account = request.getHeader(ContextConstants.JWT_KEY_ACCOUNT);
        String userid = request.getHeader(ContextConstants.JWT_KEY_USER_ID);
        String name = request.getHeader(ContextConstants.JWT_KEY_NAME);
        String projectid = request.getHeader(ContextConstants.JWT_KEY_PROJECT_ID);
        String trace = request.getHeader(ContextConstants.TRACE_ID_HEADER);

        //展会id
        String exhibitionId = request.getHeader(ContextConstants.EXHIBITION_ID);

        //语言环境
        String languageType = request.getHeader(ContextConstants.LANGUAGE_TYPE);

        //语言环境
        String terminalDevice = request.getHeader(ContextConstants.TERMINAL_DEVICE);

        //语言环境
        String terminalType = request.getHeader(ContextConstants.TERMINAL_TYPE);
        //使用主体类型
        String subjectType = request.getHeader(ContextConstants.SUBJECT_TYPE);

        if (StringUtils.isNotBlank(account)) {
            ContextUtil.setAccount(account);
        }
        if (StringUtils.isNotBlank(userid)) {
            ContextUtil.setUserId(Long.parseLong(userid));
        }
        if (StringUtils.isNotBlank(name)) {
            ContextUtil.setName(name);
        }
        if (StringUtils.isNotBlank(projectid)) {
            ContextUtil.setProjectId(Long.parseLong(projectid));
        }
        if (StringUtils.isNotBlank(trace)) {
            ContextUtil.setTrace(trace);
        }

        if (StringUtils.isNotBlank(exhibitionId)) {
            ContextUtil.setExhibitionId(Long.parseLong(exhibitionId));
        }

        /**
         * 语言环境(cn/en)
         */
        if (StringUtils.isNotBlank(languageType)) {
            ContextUtil.setLanguageType(languageType);
        }

        /**
         * 请求来源版本信息
         */
        if (StringUtils.isNotBlank(terminalDevice)) {
            ContextUtil.setTerminalDevice(terminalDevice);
        }
        /**
         * 请求来源类型 （web/app/mini_programs）
         */
        if (StringUtils.isNotBlank(terminalType)) {
            ContextUtil.setTerminalType(terminalType);
        }

        /**
         * 使用主体类型 VISITOR:观众;ACTOR:展商;SPONSOR:主办;EMPLOYEE 员工
         */
        if (StringUtils.isNotBlank(subjectType)) {
            ContextUtil.setSubjectType(subjectType);
        }

        /**
         * 指定多数据源信息
         */
        if (ContextUtil.getProjectId() != null) {
            ContextUtil.setTenant(tenantUtil.getTenant());
        }

    }

    /**
     * 处理权限
     *
     * @param joinPoint 切点
     */
    private void handleAuth(ProceedingJoinPoint joinPoint) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        // 读取权限注解，优先方法上，没有则读取类
        PreAuth preAuth = null;
        if (joinPoint.getSignature() instanceof MethodSignature) {
            method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            if (method != null) {
                preAuth = method.getAnnotation(PreAuth.class);
            }
        }
        String methodName = method != null ? method.getName() : "";
        // feign 远程调用
        if (isFeign(methodName)) {
            return;
        }
        // 方法上校验
        if (preAuth == null || !preAuth.enabled()) {
            log.debug("执行方法[{}]无需校验权限", methodName);
            return;
        }


        String[] condition = preAuth.value();
        Boolean hasPermit = invokePermit(condition, method);
        if (!hasPermit) {
            throw ForbiddenException.wrap(ExceptionCode.UNAUTHORIZED.build("执行方法[%s]需要[%s]权限", methodName, JSON.toJSONString(condition)));
        }
    }

    private boolean isFeign(String methodName) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            if (request != null) {
                if (StrPool.TRUE.equals(request.getHeader(ContextConstants.FEIGN))) {
                    log.debug("内部调用方法[{}]无需校验权限", methodName);
                    return true;
                }
            }
        }
        return false;
    }


    @Nullable
    private Boolean invokePermit(String[] condition, Method method) {
//        System.out.println(condition);
//        return true;
        R<Boolean> r = userAccountServiceFeign.isPermission(ContextUtil.getUserId(), condition);
        return r.getData();
    }

    @Nullable
    private String[] getCondition(PreAuth preAuth, PreAuth targetClass) {
        String[] condition = preAuth == null ? targetClass.value() : preAuth.value();
        return condition;
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.ac = applicationContext;
    }


    /**
     * 组装BaseControllerInfo
     */
    public void assembleBaseControllerInfo(ProceedingJoinPoint joinPoint) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        // 读取权限注解，优先方法上，没有则读取类
        BaseControllerInfo baseControllerInfo = null;
        if (method != null) {
            baseControllerInfo = method.getAnnotation(BaseControllerInfo.class);
            if (baseControllerInfo != null) {
                if (baseControllerInfo.voGroup() != null) {
                    Class[] classes = baseControllerInfo.voGroup();
                    if (classes.length > 0) {
                        DynamicData.setGroupClassThreadLocal(classes[0]);
                    }
                }
                DynamicData.setBaseControllerInfoThreadLocal(baseControllerInfo);
            }
        }
    }
}
